package no.sachmuld.methodcombination.impl.annotation;

import no.sachmuld.methodcombination.api.BehaviourChain;
import no.sachmuld.methodcombination.api.MethodCombinationProtocol;
import no.sachmuld.methodcombination.api.annotation.MethodCombinationException;
import no.sachmuld.methodcombination.api.annotation.UsesMethodCombination;
import no.sachmuld.methodcombination.api.reflection.component.MethodCombination;
import no.sachmuld.methodcombination.impl.reflection.MethodCombinationInvocationException;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Aspect
public class MethodCombinationAspect {

    public static final String POINTCUT = "execution(* @no.sachmuld.methodcombination.api.annotation.UsesMethodCombination *.*(..))";

    private Map<Method, MethodCombination> cache = new ConcurrentHashMap<>();

    @Pointcut(POINTCUT)
    public void methodWithMethodCombinationAnnotation() {
    }

    @Around("methodWithMethodCombinationAnnotation()")
    public Object intercept(final ProceedingJoinPoint thisJoinPoint) throws Throwable {

        MethodSignature sig = (MethodSignature) thisJoinPoint.getSignature();
        final Method method = sig.getMethod();
        final Class<?> targetClass = method.getDeclaringClass();

        MethodCombination methodCombination = cache.get(method);

        if (methodCombination == null) {
            methodCombination = MethodCombinationInitializationUtils.initialize(method);
            if (methodCombination != null) {
                Class behaviourClass = targetClass.getAnnotation(UsesMethodCombination.class).value();

                methodCombination.build(behaviourClass, method);

                cache.put(method, methodCombination);
            }
        }

        if (methodCombination != null) {
            Object targetObject = thisJoinPoint.getTarget();
            BehaviourChain chain = ((MethodCombinationProtocol) targetObject).chain();
            final Object[] methodParameters = thisJoinPoint.getArgs();

            try {
                return methodCombination.execute(targetObject, chain, methodParameters);
            } catch (MethodCombinationInvocationException e) {
                throw e.getCause();
            }
        } else {
            return thisJoinPoint.proceed();
        }

    }

}
